/*
** This class currently ONLY support ENGLISH language.
**
** TODO: support should be added to utf8 when going major release
*/

#include "sw_font.hpp"

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

#include <algorithm>

using namespace std;

namespace raja
{
	namespace ttf2
	{
		// use init trick to initialize and release the library on exit
		static FT_Library	__library	= NULL;

		struct __ttf2_lib
		{
			// load the library
			__ttf2_lib()
			{
				FT_Error		error;

				error	= FT_Init_FreeType(&__library);
				if( error )
				{
					cout << "Failed to initialize freetype library" << endl;
					exit(1);
				}

				cout << "freetype2 library initialized" << endl;
			}

			// release the library
			~__ttf2_lib()
			{
				FT_Done_FreeType(__library);
				cout << "freetype2 library released" << endl;
			}
		};

		// when the program starts, the freetype2 would be initialized
		static __ttf2_lib __ttf2_lib_instance;


		// giving a BITmaped part of the glyph, unpack it to chars
		static void __unpack_bits(uint8 byte, int max_w, vector<uint8> &dest)
		{
			int w	= 0;
			for( int i = 7; i >= 0 && w < max_w; i--, w++ )
			{
				if( byte & 0x80 )
				{
					//dest[w]	= 0xFF;
					dest.push_back(0xFF);
				}
				else
				{
					//dest[w] = 0x00;
					dest.push_back(0);
				}

				byte <<= 1;
			}
		}

		// get the best square texture that fits all the chars into it
		static uint32	__get_best_square_size(const ttf2::sw_font_ptr fnt_)
		{
			uint32	tex_size	= 5;	// start with 32x32 texture
			for( ; tex_size < 12; tex_size++ )
			{
				uint32	data_offset_x	= 1;
				uint32	data_offset_y	= 1;
				bool	ok	= true;

				for( size_t i = ttf2::sw_font::CHAR_START; i < ttf2::sw_font::CHAR_END && ok; i++ )
				{
					sint32	width	= fnt_->get_char_width(i);

					if( (data_offset_x + width + 1) > uint32(1 << tex_size) )
					{
						data_offset_y	+= fnt_->get_max_char_height() + 1;
						data_offset_x	= 1;

						if( (data_offset_y + fnt_->get_max_char_height() + 1) > (1 << tex_size) )
							ok	= false;
					}

					data_offset_x += width + 1;
				}

				if( ok )
					return (1 << tex_size);	// found one that is good
			}
			return 0;
		}

		///////////////////////////////////////////////////////////////////////
		// font class
		///////////////////////////////////////////////////////////////////////

		// create an instance
		sw_font_ptr sw_font::create_instance(const std::string &file_name, real32 size, bool use_hint, bool force_autohinter, bool anti_alias)
		{
			FT_Error	error;
			FT_Face		face;

			// load the font
			error	= FT_New_Face(__library,
								  file_name.c_str(),
								  0, &face);
			if( error )
			{
				cout << "Failed to load font: " << file_name << endl;
				return sw_font_ptr(static_cast<sw_font*>(NULL));
			}

			error	= FT_Set_Pixel_Sizes(face, 0, static_cast<int>(size));
			if( error )
			{
				cout << "unable to set font size to " << static_cast<int>(size) << " pixels" << endl;
				return sw_font_ptr(static_cast<sw_font*>(NULL));
			}

			// render chars
			FT_GlyphSlot	slot			= face->glyph;

			FT_Int32		load_flags		= FT_LOAD_DEFAULT;
			FT_Render_Mode	render_flags	= FT_RENDER_MODE_NORMAL;

			if( !use_hint )
				load_flags		= FT_LOAD_NO_HINTING;

			if( force_autohinter )
				load_flags		|= FT_LOAD_FORCE_AUTOHINT;
			else load_flags		|= FT_LOAD_NO_AUTOHINT;

			if( !anti_alias )
				render_flags	= FT_RENDER_MODE_MONO;

			vector<__character_info>	char_info;

			vector<uint8>		pixmap;

			math::vec2	max_char_size;

			for( uint16 i = sw_font::CHAR_START; i < sw_font::CHAR_END; i++ )
			{
				int glyph_index = FT_Get_Char_Index(face, i);

				//error	= FT_Load_Char(arial, i, FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT);
				error = FT_Load_Glyph(face, glyph_index, load_flags);
				if( error )
				{
					cout << "couldn't render char: " << i << endl;
				}

				error = FT_Render_Glyph(slot, render_flags);
				if( error )
				{
					cout << "error in rendering glyph: " << static_cast<char>(i) << endl;
				}

				__character_info	ch(slot->bitmap.width, slot->bitmap.rows);

				FT_Glyph glyph;
				FT_Get_Glyph(slot, &glyph);

				FT_BBox	box;
				FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &box);
//				cout << (char)i << " - w: " << slot->bitmap.width << ", h: " << slot->bitmap.rows <<
//					", ax: " << (slot->advance.x >> 6) << ", ay: " << (slot->advance.y >> 6) <<
//					", pitch: " << (slot->bitmap.pitch ) <<  ", bbmin( " << box.xMin << ", " << box.yMin <<
//					"), bbmax( " << box.xMax << ", " << box.yMax << ")" << endl;
//				cout << "bitmap top: " << slot->bitmap_top << " bx: " << (slot->metrics.vertBearingX >> 6) <<", by: " << (slot->metrics.vertBearingY >> 6) << endl;

				ch.char_code	= static_cast<uint8>(i);
				ch.advance	= slot->advance.x >> 6;
				ch.box_min	= /*math::ivec2(slot->bitmap_left, slot->bitmap.rows - slot->bitmap_top); */math::ivec2(box.xMin, box.yMin);
				ch.box_max	= /*math::ivec2(slot->bitmap_left + slot->bitmap.width, slot->bitmap_top);*/math::ivec2(box.xMax, box.yMax);

				max_char_size.x	= max<real32>(max_char_size.x, box.xMax - box.xMin);
				max_char_size.y	= max<real32>(max_char_size.y, box.yMax - box.yMin);

				//ch->offset	= vmath::ivec2(slot->metrics.horiBearingX >> 6, slot->metrics.horiBearingY >> 6);

				ch.pixmap_offset	= pixmap.size();

				if( !anti_alias )
				{
					for( int y = 0; y < slot->bitmap.rows; y++ )
					{
						int	w	= slot->bitmap.width;
						//uint8	*row	= &(ch->pixmap[y * slot->bitmap.width]);
						for( int x = 0; x < slot->bitmap.pitch; x++ )
						{
							__unpack_bits(slot->bitmap.buffer[x + y * slot->bitmap.pitch], w, pixmap);
							w	-= 8;
							//row	+= 8;
						}
					}
				}
				else
				{
					for( int y = 0; y < slot->bitmap.rows; y++ )
					{
						for( int x = 0; x < slot->bitmap.width; x++ )
						{
							//ch->pixmap[x + y * slot->bitmap.width] = slot->bitmap.buffer[x + y * slot->bitmap.pitch];
							pixmap.push_back(slot->bitmap.buffer[x + y * slot->bitmap.pitch]);
						}
					}
				}

				// for debugging purpose only
//				cout << "char index: " << sw_font::CHAR_START + char_info.size() << endl;

//				uint32 o = 0;
//				for( int y = 0; y < slot->bitmap.rows; y++ )
//				{
//					for( int x = 0; x < slot->bitmap.width; x++ )
//					{
//						if( pixmap[ch.pixmap_offset + o] > 127 )
//							cout << "X";
//						else if( pixmap[ch.pixmap_offset + o] > 63 )
//							cout << "x";
//						else
//							cout << ".";

//						o++;
//					}

//					cout << endl;
//				}

				char_info.push_back(ch);
			}

			// do a sanity check
			for( size_t c = 0; c < char_info.size(); c++ )
			{
				if( char_info[c].char_code != c + sw_font::CHAR_START )
				{
					cerr << "major error!!! - char: " << c << " is not " << char_info[c].char_code << endl;
					exit(0);
				}
			}

			sw_font_ptr	inst(new sw_font());

			inst->__size		= size;
			inst->__use_hint	= use_hint;
			inst->__force_autohinter	= force_autohinter;
			inst->__anti_alias	= anti_alias;
			inst->__pixmap		= pixmap;

			inst->__max_char_width	= max_char_size.x;
			inst->__max_char_height	= max_char_size.y;

			inst->__characters	= char_info;

			inst->__whole_pixmap_data.clear();


			// now create the whole pixmap
			uint32		whole_pixmap_width		= __get_best_square_size(inst);
			uint32		whole_pixmap_height		= __get_best_square_size(inst);


			assert(whole_pixmap_width != 0);

			// not necessary but the next push should go after! use resize instead of reserve
			inst->__whole_pixmap_data.resize(whole_pixmap_width * whole_pixmap_height * 4);
			std::fill_n(inst->__whole_pixmap_data.begin(), whole_pixmap_width * whole_pixmap_height * 4, 0);

			uint32	data_offset_x	= 1;
			uint32	data_offset_y	= 1;

			for( size_t i = ttf2::sw_font::CHAR_START; i < ttf2::sw_font::CHAR_END; i++ )
			{
				sint32	width	= inst->get_char_width(i);
				sint32	height	= inst->get_char_height(i);

				// only X needs to be tested for overflow, Y has been dealt with
				// in __get_best_square_size(inst);
				if( data_offset_x + width + 1 > whole_pixmap_width )
				{
					data_offset_y	+= inst->get_max_char_height() + 1;
					data_offset_x	= 1;
				}

				inst->__characters[i - ttf2::sw_font::CHAR_START].src_min	= math::vec2(data_offset_x, data_offset_y);
				inst->__characters[i - ttf2::sw_font::CHAR_START].src_max	= math::vec2(data_offset_x + width,
																					  data_offset_y + height);
				const uint8*	pixmap	= inst->get_char_pixmap(i);

				for( sint32 y = 0; y < height; y++ )
				{
					for( sint32 x = 0; x < width; x++ )
					{
						uint8	ch	= pixmap[x + y * width];
						inst->__whole_pixmap_data[0 + (data_offset_x + x + (data_offset_y + y) * whole_pixmap_width) * 4] = ch;	// R
						inst->__whole_pixmap_data[1 + (data_offset_x + x + (data_offset_y + y) * whole_pixmap_width) * 4] = ch;	// G
						inst->__whole_pixmap_data[2 + (data_offset_x + x + (data_offset_y + y) * whole_pixmap_width) * 4] = ch;	// B
						inst->__whole_pixmap_data[3 + (data_offset_x + x + (data_offset_y + y) * whole_pixmap_width) * 4] = ch;	// A
					}
				}

				data_offset_x	+= width + 1;
			}

			inst->__whole_pixmap_width	= whole_pixmap_width;
			inst->__whole_pixmap_height	= whole_pixmap_height;
			return inst;
		}


	}	// namespace ttf2
}	// namespace raja




